if __name__ == '__main__':
    import nedu
    nedu.test(__file__)

import os, math
from anoxtools import BSP
from nedu import mesh, texture, material, vector, camera
from math import cos, sin, pi

from OpenGL.GL import   glClearColor, \
                        glClear, \
                        GL_DEPTH_BUFFER_BIT, \
                        GL_COLOR_BUFFER_BIT
from OpenGL import GL
from OpenGL import GLU
from OpenGL import GLUT


fileName = "zordos.bsp" # "bricksb.bsp"
anoxPath = r"C:\Games\Anachronox\anoxdata"
mapPath = os.path.join(anoxPath, "maps")
texturePath = os.path.join(anoxPath, "textures")

class Quaternion:
    def CreateFromAxisAngle(self, x, y, z, degrees):
        # Convert the angle from degrees to radians.
        angle = (degrees / 180.0) * pi

        result = sin(angle / 2.0)
        self.w = cos(angle / 2.0)
        self.x = x * result
        self.y = y * result
        self.z = z * result

    def CreateMatrix(self):
        m = [ 0 ] * 16

        # First row.
        m[0] = 1.0 - 2.0 * (self.y * self.y + self.z * self.z)
        m[1] = 2.0 * (self.x * self.y + self.z * self.w)
        m[2] = 2.0 * (self.x * self.z - self.y * self.w)
        m[3] = 0.0

        # Second row.
        m[4] = 2.0 * (self.x * self.y - self.z * self.w)
        m[5] = 1.0 - 2.0 * (self.x * self.x + self.z * self.z)
        m[6] = 2.0 * (self.z * self.y + self.x * self.w)
        m[7] = 0.0

        # Third row.
        m[8]  = 2.0 * (self.x * self.z + self.y * self.w)
        m[9]  = 2.0 * (self.y * self.z - self.x * self.w)
        m[10] = 1.0 - 2.0 * (self.x * self.x + self.y * self.y)
        m[11] = 0.0

        # Fourth row.
        m[12] = 0
        m[13] = 0
        m[14] = 0
        m[15] = 1.0

        return m

    def __mul__(self, q2):
        q = Quaternion()
        q.w = self.w * q2.w - self.x * q2.x - self.y * q2.y - self.z * q2.z
        q.x = self.w * q2.x + self.x * q2.w + self.y * q2.z - self.z * q2.y
        q.y = self.w * q2.y + self.y * q2.w + self.z * q2.x - self.x * q2.z
        q.z = self.w * q2.z + self.z * q2.w + self.x * q2.y - self.y * q2.x
        return q

class Camera:
    def __init__(self):
        self.pitch = Quaternion()
        self.heading = Quaternion()

        self.directionVector = [ 0.0, 0.0, 0.0 ]

        self.x = 0
        self.y = 0
        self.z = -1885.0

        self.forwardVelocity = 0.0
        self.stepDistance = 0.0

        self.maxForwardVelocity = 5.0
        self.maxPitch = 5.0
        self.maxHeading = 5.0
        self.pitchDegrees = 0.0
        self.headingDegrees = 0.0

    def SetPerspective(self):
        self.pitch.CreateFromAxisAngle(1.0, 0.0, 0.0, self.pitchDegrees)
        self.heading.CreateFromAxisAngle(0.0, 1.0, 0.0, self.headingDegrees)

        m = (self.pitch * self.heading).CreateMatrix()
        self.directionVector[0] = m[2]
        self.directionVector[1] = m[6]
        self.directionVector[2] = m[10]
        GL.glMultMatrixf(m)

        if self.stepDistance:
            scalar = self.stepDistance
            self.stepDistance = 0.0
        else:
            scalar = self.forwardVelocity

        self.directionVector = [
            x * scalar
            for x
            in self.directionVector
        ]

        self.x += self.directionVector[0]
        self.y += self.directionVector[1]
        self.z += self.directionVector[2]

        GL.glTranslatef(-self.x, -self.y, -self.z)

    def ChangeHeading(self, degrees):
        if abs(degrees) > abs(self.maxHeading):
            if self.pitchDegrees > 90 and self.pitchDegrees < 270 or self.pitchDegrees < -90 and self.pitchDegrees > -270:
                self.headingDegrees -= degrees
            else:
                self.headingDegrees += degrees
        else:
            if degrees < 0:
                if self.pitchDegrees > 90 and self.pitchDegrees < 270 or self.pitchDegrees < -90 and self.pitchDegrees > -270:
                    self.headingDegrees += self.maxHeading
                else:
                    self.headingDegrees -= self.maxHeading
            else:
                if self.pitchDegrees > 90 and self.pitchDegrees < 270 or self.pitchDegrees < -90 and self.pitchDegrees > -270:
                    self.headingDegrees -= self.maxHeading
                else:
                    self.headingDegrees += self.maxHeading

    def ChangePitch(self, degrees):
        if abs(degrees) < abs(self.maxPitch):
            self.pitchDegrees += degrees
        elif degrees < 0:
            self.pitchDegrees -= self.maxPitch
        else:
            self.pitchDegrees += self.maxPitch

        if self.pitchDegrees > 360.0:
            self.pitchDegrees -= 360.0
        elif self.pitchDegrees < 360.0:
            self.pitchDegrees += 360.0


class Scene:
    keybinder = None
    lastMovement = None

    def __init__(self,demo):
        self.demo = demo

        self.camera = Camera()

        self.meshes = []
        self.textureByName = {}
        self.meshByTextureName = {}

        filePath = os.path.join(mapPath, fileName)
        f = open(filePath, 'rb')
        try:
            self.mapInfo = BSP.BSPReader(f, anoxPath, filePath)
        finally:
            f.close()

        for fi in range(len(self.mapInfo.faces)):
            try:
                l = self.mapInfo.GetTrianglesForFace(fi)
            except:
                l = []
            if not len(l):
                print "BAD FACE", fi
                continue

            textureName, material = self.GetTexture(fi)
            m = self.meshByTextureName.get(textureName, None)
            if m is None:
                m = mesh.Mesh()
                m.set_material(0, material)

            face = self.mapInfo.faces[fi]
            plane = self.mapInfo.planes[face.planeNum]

            for j in range(0, len(l), 3):
                # Note what will be the offset of the first vertex.
                vi = len(m.vertices)
                ti = len(m.texcoords)
                ni = len(m.normals)

                normal = plane.normal
                if face.side:
                    normal = [ -v for v in normal ]

                # Register the vertices.
                m.add_vertex(*l[j].point)
                m.add_texcoord(*l[j].texCoord)
                m.add_normal(*plane.normal)

                m.add_vertex(*l[j+1].point)
                m.add_texcoord(*l[j+1].texCoord)
                m.add_normal(*plane.normal)

                m.add_vertex(*l[j+2].point)
                m.add_texcoord(*l[j+2].texCoord)
                m.add_normal(*plane.normal)

                # Register the face.
                m.add_face(vi, vi+1, vi+2)
                m.add_texcoord_face(ti, ti+1, ti+2)
                m.add_normal_face(ni, ni+1, ni+2)

            m.upload()
            self.meshes.append(m)

        GLUT.glutMotionFunc(self.MouseMovement)
        GLUT.glutMouseFunc(self.Mouse)

        self.BindKey("w", self.camera.ChangePitch, -5.0)
        self.BindKey("s", self.camera.ChangePitch,  5.0)
        self.BindKey("a", self.camera.ChangeHeading, -5.0)
        self.BindKey("d", self.camera.ChangeHeading,  5.0)
        self.BindKey("r", self.MoveForward, -130.0)
        self.BindKey("f", self.MoveForward,  130.0)
        self.BindKey(" ", self.PrintCamera)

    def Mouse(self, button, state, x, y):
        if button == 0:
            if state == 0:
                self.lastMovement = x, y
            elif state == 1:
                self.lastMovement = None
        # print "click", button, state, x, y, self.lastMovement

    def MouseMovement(self, x, y):
        # print "move new", self.lastMovement, "old", (x, y), self.camera.pitchDegrees, self.camera.headingDegrees
        if self.lastMovement is not None:
            lastX, lastY = self.lastMovement

            if x != lastX:
                delta = x - lastX
                self.camera.ChangeHeading(delta)

            if y != lastY:
                delta = y - lastY
                self.camera.ChangePitch(delta)

            self.lastMovement = x, y

    def MoveForward(self, v):
        self.camera.stepDistance = v

    # Want to have mouse movements change the camera direction.
    # And maybe left mouse move the camera forward.
    # Right mouse move the camera backward.

    def PrintCamera(self):
        print self.camera.x, self.camera.y, self.camera.z

    def SetXRotate(self, v):
        self.camera.rotate_x = v

    def SetZRotate(self, v):
        self.camera.rotate_z = v

    def BindKey(self, *args, **kargs):
        if not self.keybinder:
            from nedu import keybind
            self.keybinder = keybind.KeyBinder()
            self.keybinder.add_frame(self.demo.frame)
        self.keybinder.bind_key(*args, **kargs)

    def GetTexture(self, faceIdx):
        face = self.mapInfo.faces[faceIdx]
        texInfo = self.mapInfo.texInfo[face.texInfo]

        textureName = texInfo.textureName.lower()
        if not self.textureByName.has_key(textureName):
            path = os.path.join(texturePath, textureName) +".tga"
            mat = material.Material()
            mat.diffuse_texture = texture.Texture(path=path, levels=1)
            self.textureByName[textureName] = mat
        return textureName, self.textureByName[textureName]

    def render(self):
        time = self.demo.time
        camera = self.camera

        glClearColor(0.5,0.5,0.5,1.0)
        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT)

        GL.glMatrixMode(GL.GL_PROJECTION)
        GL.glLoadIdentity()

        GLU.gluPerspective(45.0, 1.33, 0.1, 100000.0)

        camera.SetPerspective()

        GL.glEnable(GL.GL_LIGHTING)
        GL.glEnable(GL.GL_LIGHT0)
        GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, (camera.x, camera.y, camera.z))
        GL.glEnable(GL.GL_LIGHT1)
        GL.glLightfv(GL.GL_LIGHT1, GL.GL_POSITION, (camera.x, camera.y, camera.z))

        GL.glEnable(GL.GL_NORMALIZE)
        GL.glDepthFunc(GL.GL_LEQUAL)
        GL.glEnable(GL.GL_DEPTH_TEST)
        GL.glEnable(GL.GL_CULL_FACE)
        GL.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST)

        GL.glMatrixMode(GL.GL_MODELVIEW)
        GL.glLoadIdentity()
        GL.glScalef(1.0,1.0,1.0)

        #GL.glTranslatef(0.0,-0.5,0.0)
        #GL.glRotatef(time *22.5, 0.0, 1.0, 0.0 )
        #GL.glTranslatef(-2.0,0.0,0.0)

        for mesh in self.meshes:
            mesh.render()
